package com.gmadmin.configuration.secutity.filter;

import com.gmadmin.common.HttpStatus;
import com.gmadmin.common.Inner;
import com.gmadmin.configuration.secutity.ignore.CustomConfig;
import com.gmadmin.exception.SecurityException;
import com.gmadmin.service.SysApiService;
import com.gmadmin.entity.vo.api.SysApiVo;
import com.gmadmin.utils.ResponseUtil;

import com.gmadmin.utils.jwt.JwtUtil;
import io.lettuce.core.RedisCommandTimeoutException;
import org.assertj.core.util.Sets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.annotation.Nonnull;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * @author zhangliang
 * @version 1.0
 * @date 2021/2/2 12:40
 */
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    protected static final Logger log = LoggerFactory.getLogger(JwtAuthenticationFilter.class);

    private final UserDetailsService customUserDetailsService;

    private final JwtUtil jwtUtil;

    private final CustomConfig customConfig;

    private final SysApiService sysApiService;

    private final RequestMappingHandlerMapping requestMappingHandlerMapping;


    public JwtAuthenticationFilter(UserDetailsService customUserDetailsService, JwtUtil jwtUtil, CustomConfig customConfig, SysApiService sysApiService, RequestMappingHandlerMapping requestMappingHandlerMapping) {
        this.customUserDetailsService = customUserDetailsService;
        this.jwtUtil = jwtUtil;
        this.customConfig = customConfig;
        this.sysApiService = sysApiService;
        this.requestMappingHandlerMapping = requestMappingHandlerMapping;
    }

    /**
     * //log.debug(this.getClass().getName()+":{}","jwt拦截器配置");
     */
    @Override
    protected void doFilterInternal(@Nonnull HttpServletRequest httpServletRequest, @Nonnull HttpServletResponse httpServletResponse, @Nonnull FilterChain filterChain) throws ServletException, IOException {
        //查询此地址是否存在数据库并且是白名单
        SysApiVo api = sysApiService.findApiByPathAndMethod(httpServletRequest.getRequestURI(), httpServletRequest.getMethod());

        if (checkIgnores(httpServletRequest, api)) {
            log.debug("白名单验证通过");
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }
        String jwt = jwtUtil.getJwtFromRequest(httpServletRequest);

        if (StringUtils.hasLength(jwt) && Objects.nonNull(api)) {
            try {
                String username = jwtUtil.getUsernameFromJWT(jwt);

                UserDetails userDetails = customUserDetailsService.loadUserByUsername(username);
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));

                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//                   验证通过才到controller
                log.debug("路由授权验证通过");
                filterChain.doFilter(httpServletRequest, httpServletResponse);
            } catch (SecurityException e) {
                log.info("security异常");
                ResponseUtil.renderJson(httpServletResponse, HttpStatus.LOGIN_HAS_EXPIRATION, httpServletRequest.getRequestURI());
            } catch (RedisCommandTimeoutException e) {
                log.info("RedisCommandTimeoutException:  Command timed out after 240 millisecond(s)");
            }
        } else {
            //是否已经提交响应
            if (!httpServletResponse.isCommitted()) {
                if (Objects.isNull(api)) {
                    ResponseUtil.renderJson(httpServletResponse, HttpStatus.API_NOT_FOUND, httpServletRequest.getRequestURI());
                } else {
                    ResponseUtil.renderJson(httpServletResponse, HttpStatus.UNAUTHORIZED, null);
                }
            }
        }

    }


    /**
     * 请求是否不需要进行权限拦截
     *
     * @param request 当前请求
     * @return true - 忽略，false - 不忽略
     */
    private boolean checkIgnores(HttpServletRequest request, SysApiVo api) {
        String method = request.getMethod();

        Set<String> ignores = Sets.newHashSet();

        if (Objects.nonNull(api) && Boolean.TRUE.equals(api.getyIgnore())) {
            ignores.add(api.getPath());
        }
        // 这是配置文件读取白名单，留用
        HttpMethod httpMethod = HttpMethod.resolve(method);
        if (Objects.isNull(httpMethod)) {
            httpMethod = HttpMethod.GET;
        }
        if (Objects.nonNull(customConfig.getIgnoreConfig())) {
            switch (httpMethod) {
                case GET:
                    ignores.addAll(customConfig.getIgnoreConfig().getGet());
                    break;
                case PUT:
                    ignores.addAll(customConfig.getIgnoreConfig().getPut());
                    break;
                case HEAD:
                    ignores.addAll(customConfig.getIgnoreConfig().getHead());
                    break;
                case POST:
                    ignores.addAll(customConfig.getIgnoreConfig().getPost());
                    break;
                case PATCH:
                    ignores.addAll(customConfig.getIgnoreConfig().getPatch());
                    break;
                case TRACE:
                    ignores.addAll(customConfig.getIgnoreConfig().getTrace());
                    break;
                case DELETE:
                    ignores.addAll(customConfig.getIgnoreConfig().getDelete());
                    break;
                case OPTIONS:
                    ignores.addAll(customConfig.getIgnoreConfig().getOptions());
                    break;
                default:
                    log.info("没有白名单配置");
                    break;
            }

            ignores.addAll(customConfig.getIgnoreConfig().getPattern());

        }

        // 注解白名单放行
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
        handlerMethods.forEach((info, methods) -> {
            // 带IgnoreAuth注解的方法直接放行
            if (com.gmadmin.utils.StringUtils.isNotNull(methods.getMethodAnnotation(Inner.class))) {
                    ignores.addAll(info.getPatternsCondition().getPatterns());
            }
        });

        if (!ignores.isEmpty()) {
            for (String ignore : ignores) {
                AntPathRequestMatcher matcher = new AntPathRequestMatcher(ignore, method);

                if (matcher.matches(request)) {
                    return true;
                }
            }
        }
        return false;
    }
}
